MoveViz is a package that consists of a set of tools used for visualizing movement data into animated trajectories
Movement trajectories used for ggplots
Spatial data containing x and y geolocation points for plots and maps is integrated with time stamp data (temporal data) forming spatio-temporal data
Move, raster class inputs or movestack data added to baselayer map for ggplot frames
MoveVis functions to animate movement trajectories using different time intervals
Frames rendered into animations encoded as GIF or video file,
Many of us in Biology programs conduct research consisting of track data for marine life such as sharks, dolphins and fish.
Migration of cattle, bears, birds, snakes, lizards and endangered species.
Population data i.e. human migration movement data across continents over time
Visually explore and interpreting movement patterns
Potential interactions of individuals with each other and their environment
Climate data, tornados, storms, rain & wind, tracking major weather events
Historical data, migration, climate, environmental
Stock data
Can be used to track sports races/events such as bike races, cars, marathons etc
Time resolution could be 500 years for human migration or 5 seconds for bike race data
To produce a ggplot or video output containing animated movement data, 4 main functions must be carried out
Each of which have sub functions within their category to further optimize the output features of the visual.
There are additional features that function by manipulating and changing the data such as those in the tidyverse package
library(tidyverse)
library(here)
library(tidytuesdayR)
library(lubridate)
To Download moveVis : install.packages(“moveVis”)
# install.packages("moveVis")
library(moveVis)
To demostrate the abilities of this package, we will use Pet Cats UK dataset from TidyTuesday
Will use the “cats_uk” dataset
tuesdata <- tidytuesdayR::tt_load(2023, week = 5)
##
## Downloading file 1 of 2: `cats_uk.csv`
## Downloading file 2 of 2: `cats_uk_reference.csv`
cats_uk <- as.data.frame(tuesdata$cats_uk)
glimpse(cats_uk)
## Rows: 18,215
## Columns: 11
## $ tag_id <chr> "Ares", "Ares", "Ares", "Ares", "Ares", "Ares…
## $ event_id <dbl> 3395610551, 3395610552, 3395610553, 339561055…
## $ visible <lgl> TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRU…
## $ timestamp <dttm> 2017-06-24 01:03:57, 2017-06-24 01:11:20, 20…
## $ location_long <dbl> -5.113851, -5.113851, -5.113730, -5.113774, -…
## $ location_lat <dbl> 50.17032, 50.17032, 50.16988, 50.16983, 50.17…
## $ ground_speed <dbl> 684, 936, 2340, 0, 4896, 504, 108, 504, 252, …
## $ height_above_ellipsoid <dbl> 154.67, 154.67, 81.35, 67.82, 118.03, 123.07,…
## $ algorithm_marked_outlier <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FAL…
## $ manually_marked_outlier <lgl> FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FAL…
## $ study_name <chr> "Pet Cats United Kingdom", "Pet Cats United K…
Needs to be a times data
Cannot have duplicated time stamp
cats_uk <- cats_uk%>%
mutate( timestamp = ymd_hms(timestamp)) %>%
filter(tag_id %in% c("Athena","Ares", "Lola")) #simplify dataset to managable practice set
If there is duplicated timestamp, needs to be removed
cats_uk <- cats_uk[!duplicated(cats_uk$timestamp),]
First have to convert a data.frame to move or moveStack using df2move()
Our required compnents of the data will be specified here
cat <- df2move(cats_uk, #df
proj = "+init=epsg:4326 +proj=longlat +datum=WGS84 +no_defs +ellps=WGS84 +towgs84=0,0,0", #type of projection
x = "location_long", #what are your x coord
y = "location_lat",
time = "timestamp", #what are the time, needs to be POSIXct
track_id = "tag_id"
)
glimpse(cat)
## Formal class 'MoveStack' [package "move"] with 17 slots
## ..@ trackId : Factor w/ 3 levels "Ares","Athena",..: 1 1 1 1 1 1 1 1 1 1 ...
## ..@ timestamps : POSIXct[1:432], format: "2017-06-24 01:03:57" "2017-06-24 01:11:20" ...
## ..@ idData :'data.frame': 3 obs. of 0 variables
## ..@ sensor : Factor w/ 1 level "unknown": 1 1 1 1 1 1 1 1 1 1 ...
## ..@ data :'data.frame': 432 obs. of 3 variables:
## .. ..$ x : num [1:432] -5.11 -5.11 -5.11 -5.11 -5.11 ...
## .. ..$ y : num [1:432] 50.2 50.2 50.2 50.2 50.2 ...
## .. ..$ time: POSIXct[1:432], format: "2017-06-24 01:03:57" "2017-06-24 01:11:20" ...
## ..@ coords.nrs : num(0)
## ..@ coords : num [1:432, 1:2] -5.11 -5.11 -5.11 -5.11 -5.11 ...
## .. ..- attr(*, "dimnames")=List of 2
## ..@ bbox : num [1:2, 1:2] -5.12 50.14 -5.07 50.17
## .. ..- attr(*, "dimnames")=List of 2
## ..@ proj4string :Formal class 'CRS' [package "sp"] with 1 slot
## ..@ trackIdUnUsedRecords : Factor w/ 3 levels "Ares","Athena",..:
## ..@ timestampsUnUsedRecords: NULL
## ..@ sensorUnUsedRecords : Factor w/ 1 level "unknown":
## ..@ dataUnUsedRecords :'data.frame': 0 obs. of 0 variables
## ..@ dateCreation : POSIXct[1:1], format: "2023-03-25 21:21:29"
## ..@ study : chr(0)
## ..@ citation : chr(0)
## ..@ license : chr(0)
To be able to convert data into frames, it needs to be consistent time intervals
m <- align_move(cat,
res = 10, #specify resolution
unit = "mins") #resolution unit
glimpse(m)
## Formal class 'MoveStack' [package "move"] with 17 slots
## ..@ trackId : Factor w/ 3 levels "Ares","Athena",..: 1 1 1 1 1 1 1 1 1 1 ...
## ..@ timestamps : POSIXct[1:2903], format: "2017-06-24 01:10:13" "2017-06-24 01:20:13" ...
## .. ..- attr(*, "names")="Ares1" "Ares2" ...
## ..@ idData :'data.frame': 3 obs. of 0 variables
## ..@ sensor : Factor w/ 2 levels "unknown","interpolateTime": 2 2 2 2 2 2 2 2 2 2 ...
## .. ..- attr(*, "names")= chr [1:2903] "Ares1" "Ares2" "Ares3" "Ares4" ...
## ..@ data :'data.frame': 2903 obs. of 3 variables:
## .. ..$ x : num [1:2903] -5.11 -5.11 -5.11 -5.11 -5.11 ...
## .. ..$ y : num [1:2903] 50.2 50.2 50.2 50.2 50.2 ...
## .. ..$ time: POSIXct[1:2903], format: "2017-06-24 01:10:13" "2017-06-24 01:20:13" ...
## ..@ coords.nrs : num(0)
## ..@ coords : num [1:2903, 1:2] -5.11 -5.11 -5.11 -5.11 -5.11 ...
## .. ..- attr(*, "dimnames")=List of 2
## ..@ bbox : num [1:2, 1:2] -5.12 50.14 -5.07 50.17
## .. ..- attr(*, "dimnames")=List of 2
## ..@ proj4string :Formal class 'CRS' [package "sp"] with 1 slot
## ..@ trackIdUnUsedRecords : Factor w/ 3 levels "Ares","Athena",..:
## ..@ timestampsUnUsedRecords: NULL
## ..@ sensorUnUsedRecords : Factor w/ 2 levels "unknown","interpolateTime":
## ..@ dataUnUsedRecords :'data.frame': 0 obs. of 0 variables
## ..@ dateCreation : POSIXct[1:1], format: "2023-03-25 21:21:29"
## ..@ study : chr(0)
## ..@ citation : chr(0)
## ..@ license : chr(0)
library(move)
You can use get_maptypes() get a list of all available map_services and map_types
get_maptypes()
## $osm
## [1] "streets" "streets_de" "streets_fr" "humanitarian" "topographic"
## [6] "roads" "hydda" "hydda_base" "hike" "grayscale"
## [11] "no_labels" "watercolor" "toner" "toner_bg" "toner_lite"
## [16] "terrain" "terrain_bg" "mtb"
##
## $carto
## [1] "light" "light_no_labels" "light_only_labels"
## [4] "dark" "dark_no_labels" "dark_only_labels"
## [7] "voyager" "voyager_no_labels" "voyager_only_labels"
## [10] "voyager_labels_under"
##
## $mapbox
## [1] "satellite" "streets" "streets_basic" "hybrid"
## [5] "light" "dark" "high_contrast" "outdoors"
## [9] "hike" "wheatpaste" "pencil" "comic"
## [13] "pirates" "emerald"
frames <- frames_spatial(
m = m, # input data
trace_show = TRUE, # show trace of complete path
equidistant = FALSE, # make map square (FALSE = prevent stretching of map)
map_service = "osm", # select map service
map_type = "streets", # select map type
alpha = 0.75, # select transparency level for map
path_colours = c("#D55E00", "#009E73", "#56B4E9"), # select colors for paths
ext = extent(-5.12, -5.065, 50.14, 50.175), # define a custom extent for map
map_res = 0.8, # select map resolution
)
## Checking temporal alignment...
## Processing movement data...
## Approximated animation duration: ≈ 39.84s at 25 fps for 996 frames
## Retrieving and compositing basemap imagery...
## Assigning raster maps to frames...
## Creating frames...
You can use length() to check the number of frames
length(frames)
## [1] 996
You can use frames[[]] to view a single frame at a time
frames[[100]]
# Adapting
spatial frames
You can use add_labels() to add labels to frames
frames <- add_labels(frames, x = "Longitude", y = "Latitude") # add axis labels
frames[[100]]
You can use add_timestamps() to show timestamps on frames
frames <- add_timestamps(frames, type = "label")
frames[[100]]
You can use add_progress() to add a progress bar to frames
frames <- add_progress(frames, colour = "red")
frames[[100]]
You can use add_northarrow() to add a north arrow to frames
frames <- add_northarrow(frames, colour = "black", height = 0.08, position = "bottomleft")
frames[[100]]
You can use add_scalebar() to add a scalebar to frames
frames <- add_scalebar(frames, colour = "black", position = "bottomright", height = 0.022, label_margin = 1.4, distance = 1)
frames[[100]]
You can include additional visual features in frames
# Make data.frame() with coordinates for vertices of a polygon
data <- data.frame(x = c(-5.11, -5.10, -5.10, -5.11, -5.11),
y = c(50.160, 50.160, 50.165, 50.165, 50.160))
# You can customize individual frames with geom_path()
modified_frame <- frames[[100]] + geom_path(aes(x = x, y = y), data = data, colour = "red", linetype = "dashed")
modified_frame
You can use add_gg() to customize all of the frames at once
frames <- add_gg(frames, gg = expr(geom_path(aes(x = x, y = y), data = data, color = "red", linetype = "dashed")), data = data)
frames[[100]]
You can also use add_gg() to add dynamic/animated features to frames
# create data.frame containing coordinates for polygon vertices
data <- data.frame(x = c(-5.08, -5.09, -5.09, -5.08, -5.08),
y = c(50.150, 50.150, 50.155, 50.155, 50.150))
# make a list from the data.frame by replicating it by the length of frames
data <- rep(list(data), length.out = length(frames))
# alter the coordinates to make them shift
data <- lapply(data, function(x){
y <- rnorm(nrow(x)-1, mean = 0.00001, sd = 0.0001)
x + c(y, y[1])
})
# draw each individual polygon to each frame
frames <- add_gg(frames, gg = expr(geom_path(aes(x = x, y = y), data = data, colour = "black")), data = data)
frames[[100]]